<?php
// +----------------------------------------------------------------------
// | INPHP
// | Copyright (c) 2023 https://inphp.cc All rights reserved.
// | Author: 幺月儿(https://gitee.com/lulanyin) Email: inphp@qq.com
// | 该文件的开源协议以所在项目的LICENSE文件为准，请遵守开源协议要求
// +----------------------------------------------------------------------
// | 收银记录
// +----------------------------------------------------------------------
namespace app\finance\model;

use Inphp\Core\Db\Db;
use Inphp\Core\Db\PDO\Model;
use Inphp\Core\Middlewares;
use Inphp\Core\Modules;
use Inphp\Core\Object\Message;

class CashierModel extends Model
{
    /**
     * 主表
     * @var string
     */
    protected string $tableName = "finance_cashier";

    /**
     * 主键
     * @var string
     */
    protected string $primaryKey = "id";

    /**
     * 获取订单类型
     * @param string $prefix
     * @return array
     */
    public static function orderTypes(string $prefix = ""): array
    {
        $config = getFinanceConfig("cashier");
        $config = is_array($config) ? $config : [];
        $orderTypes = $config[!empty($prefix) ? "{$prefix}OrderTypes" : "orderTypes"] ?? [];
        return is_array($orderTypes) ? $orderTypes : [];
    }

    /**
     * 获取外部订单类型
     * @return array
     */
    public static function outsideOrderTypes(): array
    {
        return self::orderTypes("outside");
    }

    /**
     * 获取支持API创建收银订单的订单类型
     * @return array
     */
    public static function generateOrderTypes(): array
    {
        return self::orderTypes("generate");
    }

    /**
     * 获取可使用余额支付的订单类型，该类订单必须支持API创建
     * @return array
     */
    public static function balancePayOrderTypes(): array
    {
        return self::orderTypes("balancePay");
    }

    /**
     * 获取订单名称
     * @param string $orderType
     * @return string
     */
    public static function orderName(string $orderType): string
    {
        return self::orderTypes()[$orderType] ?? "未知订单类型";
    }

    /**
     * 获取默认货币
     * @return string
     */
    public static function currency(): string
    {
        $currencyList = getFinanceConfig("currency");
        return array_keys($currencyList)[0];
    }

    /**
     * 获取货币名称
     * @param string|null $currency
     * @return string
     */
    public static function currencyName(?string $currency = null): string
    {
        $currencyList = getFinanceConfig("currency");
        $currency = $currency ?? array_keys($currencyList)[0];
        if (isset($currencyList[$currency])) {
            return $currencyList[$currency]["name"];
        }
        return "人民币";
    }

    /**
     * 获取货币单位
     * @param string|null $currency
     * @return string
     */
    public static function currencyUnit(?string $currency = null): string
    {
        $currencyList = getFinanceConfig("currency");
        $currency = $currency ?? array_keys($currencyList)[0];
        if (isset($currencyList[$currency])) {
            return $currencyList[$currency]["unit"];
        }
        return "元";
    }

    /**
     * 所有支付方式
     * @return array
     */
    public static function payments(): array
    {
        $payments = BalanceModel::names();
        $payments["wechat"] = "微信在线支付";
        $payments["alipay"] = "支付宝在线支付";
        //自定义
        $otherPayments = getFinanceConfig("cashier.otherPayments");
        $otherPayments = is_array($otherPayments) && !empty($otherPayments) ? $otherPayments : [];
        if (!empty($otherPayments)) {
            $payments = array_merge($payments, $otherPayments);
        }
        return $payments;
    }

    /**
     * 获取在线支付方式
     * @return array
     */
    public static function onlinePayments(): array
    {
        $onlinePayments = getFinanceConfig("cashier.onlinePayments");
        return is_array($onlinePayments) ? $onlinePayments : [];
    }

    /**
     * 获取支付方式名称
     * @param string|null $payment
     * @return string
     */
    public static function getPayment(?string $payment): string
    {
        if (empty($payment)) return '-';
        $payments = self::payments();
        if (stripos($payment, ",") > 0) {
            $strs = explode(",", $payment);
            $result = [];
            foreach ($strs as $str) {
                $result[] = $payments[$str] ?? "未知";
            }
            return "多种(".join("|", $result).")";
        }
        return $payments[$payment] ?? "未知";
    }

    /**
     * 手动支付
     * @param int $uid
     * @param int $orderId
     * @param string $orderType
     * @param float|int $amount
     * @param string $balance
     * @return bool
     */
    public static function pay(int $uid, int $orderId, string $orderType, float|int $amount, string $balance, int $editor = 0): bool
    {
        $myBalance = BalanceModel::getBalance($uid);
        $max = $myBalance[$balance] ?? 0;
        if ($amount > $max) {
            return false;
        }
        $cashier = [
            "id"        => makeOrderId(),
            "uid"       => $uid,
            "orderId"   => $orderId,
            "orderType" => $orderType,
            "amount"    => $amount,
            "currency"  => $balance,
            "currencyRatio" => 1,
            "ip"        => getIP()
        ];
        Db::begin();
        $db = CashierModel::emptyQuery();
        if ($db->insert($cashier)) {
            //扣款
            if (BalanceModel::decrement($uid, $balance, $amount, $orderType, null, $orderId, $editor)) {
                $res = self::payed($cashier["id"], $amount, '', $uid, $balance);
                if ($res->error === 0) {
                    Db::commit();
                    return true;
                }
            }
        }
        Db::rollback();
        return false;
    }

    /**
     * 支付成功
     * @param int $cashierId
     * @param float|int $payedAmount
     * @param string $tradeNo
     * @param string|null $payerId
     * @param string|null $payment
     * @param string|null $paymentAppId
     * @param string|null $paymentResult
     * @param bool $dev
     * @return Message
     */
    public static function payed(int $cashierId, float|int $payedAmount, string $tradeNo, ?string $payerId = null, ?string $payment = null, ?string $paymentAppId = null, ?string $paymentResult = null, bool $dev = false): Message
    {
        $cashier = self::getRowByPrimaryKey($cashierId);
        if (empty($cashier)) {
            return httpMessage("收银单不存在");
        }
        if (!empty($tradeNo) && in_array($tradeNo, explode(",", $cashier["payedTradeNo"]))) {
            return httpMessage(0, "重复的交易流水号");
        }
        $db = self::emptyQuery()->where("id", $cashierId)
            ->increment("payedAmount", $payedAmount)
            ->setRaw("payed", "if(`payedAmount`>=`amount`, 1, `payed`)")
            ->set("payedTime", date("Y-m-d H:i:s"));
        if (!empty($tradeNo)) {
            $db->setRaw("payedTradeNo", "if(`payedTradeNo` is null or `payedTradeNo`='', '{$tradeNo}', concat(`payedTradeNo`, ',{$tradeNo}'))");
        }
        //若存在支付方式
        if (!empty($payment)) {
            $db->setRaw("payment", "if(`payment` is null or `payment`='', '{$payment}', concat(`payment`, ',{$payment}'))");
        }
        //若存在不同的支付APPID，为了方便更好的匹配退款
        if (!empty($paymentAppId)) {
            $db->setRaw("paymentAppId", "if(`paymentAppId` is null or `paymentAppId`='', '{$paymentAppId}', concat(`paymentAppId`, ',{$paymentAppId}'))");
        }
        //若存在支付回调数据，保存一下，虽然有点占地方
        if (!empty($paymentResult)) {
            $db->setRaw("payedResult", "if(`payedResult` is null, '{$paymentResult}', concat(`payedResult`, '__payment_result__{$paymentResult}'))");
        }
        //若更新失败
        if (!$db->update()) {
            return httpMessage("更新收银单失败");
        }
        //执行付款成功通知
        Middlewares::process(FINANCE_PAYED_NOTIFY, [
            "orderType"     => $cashier["orderType"],
            "orderId"       => $cashier["orderId"],
            "payment"       => $payment,
            "cashierId"     => $cashierId,
            "payedAmount"   => $dev ? bcsub($cashier['amount'], $cashier['payedAmount'], 2) : $payedAmount
        ]);
        @self::emptyQuery()->where("id", $cashierId)->increment("notify")->update();
        return httpMessage(0, "处理结束");
    }

    /**
     * 更新接收通知次数
     * @param int $cashierId
     */
    public static function payedNotified(int $cashierId)
    {
        @self::emptyQuery()->where("id", $cashierId)->increment("notified")->update();
    }

    /**
     * 退款
     * 执行后自动处理，并且会异步通知到原订单，返回值包含退款ID和实际退款金额
     * @param array $cashier
     * @param float|int $amount
     * @param string|null $bak
     * @param int $editor
     * @param bool $confirm
     * @return Message
     */
    public static function refund(array $cashier, float|int $amount, string $bak = null, int $editor = 0, bool $confirm = false): Message
    {
        if ($amount <= 0) {
            return httpMessage("退款数额必须大于0");
        }
        if ($cashier["payedAmount"] <= 0) {
            return httpMessage("退款失败，可退款数额为0");
        }
        $maxAmount = bcsub($cashier["payedAmount"], $cashier["refundAmount"], 2);
        //查询正在退款的单
        $refundTotal = RefundModel::emptyQuery()
            ->where("cashierId", $cashier['id'])
            ->whereNotIn("state", [0,1])
            ->sum("amount");
        $refundTotal = !empty($refundTotal) ? $refundTotal : 0;
        $maxAmount = bcsub($maxAmount, $refundTotal, 2);
        if ($maxAmount < $amount) {
            return httpMessage("退款失败，可退款数额为：{$maxAmount}，不足：{$amount}");
        }
        $refund = [
            "id"            => makeOrderId(),
            "cashierId"     => $cashier["id"],
            "amount"        => min($amount, $maxAmount),
            "uid"           => $cashier["uid"],
            "editor"        => $editor,
            "bak"           => $bak,
            "payment"       => $cashier["payment"],
            "state"         => 2,
            "refund"        => 0
        ];
        Db::begin();
        $db = RefundModel::emptyQuery();
        if (!$db->insert($refund)) {
            Db::rollback();
            return httpMessage("退款失败，未能保存退款记录");
        }
        $refundId = $refund["id"];
        /*
        //更新收银单据
        $cdb = self::emptyQuery()
            ->where("id", $cashier["id"])
            ->increment("refundAmount", $refund["amount"])
            ->setRaw("refundId", "if(`refundId` is null or `refundId`='', '{$refundId}', concat(`refundId`, ',{$refundId}'))")
            ->set("refund", 1)
            ->set("refundTime", date("Y-m-d H:i:s"));
        if (!$cdb->update()) {
            Db::rollback();
            return httpMessage("退款失败，未能更新收银单");
        }*/
        Db::commit();
        //是否需要确认
        $config = Modules::getModule("finance")->getConfig("refund");
        if (!$config["needConfirm"] || $confirm) {
            //无需确认
            $res = RefundModel::confirm(array_merge($refund, [
                //订单实际支付的金额
                "payedAmount"   => $cashier["payedAmount"],
                //第三方交易单号
                "payedTradeNo"  => $cashier["payedTradeNo"],
                //第三方交易APPID
                "paymentAppId"  => $cashier["paymentAppId"],
                //订单类型
                "orderType"     => $cashier["orderType"],
                //订单号
                "orderId"       => $cashier["orderId"]
            ]));
            $refundAmount = is_array($res) ? $res["refundAmount"] : 0;
            if (!$res || $refundAmount <= 0) {
                //作废
                @RefundModel::emptyQuery()->where("id", $refundId)->update(["state" => 0, "refund" => 0]);
                return httpMessage("退款单已生成，但退款处理时未成功");
            }
            $refund['amount'] = $refundAmount;
        }
        //返回退款成功ID和实际退款金额
        return httpMessage(0, "退款处理结束", [
            "refundId"      => $refundId,
            "refundAmount"  => $refund["amount"]
        ]);
    }

    /**
     * 更新接收通知次数
     * @param int $refundId
     */
    public static function refundNotified(int $refundId)
    {
        @RefundModel::emptyQuery()->where("id", $refundId)->increment("notified")->update();
    }

    /**
     * 使用收银单ID进行退款
     * @param int|null $cashierId
     * @param float|int $amount
     * @param string|null $bak
     * @param int $editor
     * @return Message
     */
    public static function refundByCashierId(?int $cashierId, float|int $amount, string $bak = null, int $editor = 0, bool $confirm = false): Message
    {
        if (empty($cashierId)) {
            return httpMessage("缺少需要退款的收银单ID");
        }
        $cashier = self::getRowByPrimaryKey($cashierId);
        if (empty($cashier)) {
            return httpMessage("未找到对应的收银单");
        }
        return self::refund($cashier, $amount, $bak, $editor, $confirm);
    }
}